/*
 * contrib/intarray/_int_op.c
 */
#include "postgres.h"

#include "_int.h"

PG_MODULE_MAGIC;

PG_FUNCTION_INFO_V1(_int_different);
PG_FUNCTION_INFO_V1(_int_same);
PG_FUNCTION_INFO_V1(_int_contains);
PG_FUNCTION_INFO_V1(_int_contained);
PG_FUNCTION_INFO_V1(_int_overlap);
PG_FUNCTION_INFO_V1(_int_union);
PG_FUNCTION_INFO_V1(_int_inter);

Datum _int_contained(PG_FUNCTION_ARGS)
{
	/* 只是反转操作数并调用 _int_contains */
	return DirectFunctionCall2(_int_contains,
							   PG_GETARG_DATUM(1),
							   PG_GETARG_DATUM(0));
}

Datum _int_contains(PG_FUNCTION_ARGS)
{
	/* 强制复制，以便我们可以就地修改数组 */
	ArrayType  *fc_a = PG_GETARG_ARRAYTYPE_P_COPY(0);
	ArrayType  *fc_b = PG_GETARG_ARRAYTYPE_P_COPY(1);
	bool		fc_res;

	CHECKARRVALID(fc_a);
	CHECKARRVALID(fc_b);
	PREPAREARR(fc_a);
	PREPAREARR(fc_b);
	fc_res = inner_int_contains(fc_a, fc_b);
	pfree(fc_a);
	pfree(fc_b);
	PG_RETURN_BOOL(fc_res);
}

Datum _int_different(PG_FUNCTION_ARGS)
{
	PG_RETURN_BOOL(!DatumGetBool(DirectFunctionCall2(_int_same,
													 PointerGetDatum(PG_GETARG_POINTER(0)),
													 PointerGetDatum(PG_GETARG_POINTER(1)))));
}

Datum _int_same(PG_FUNCTION_ARGS)
{
	ArrayType  *fc_a = PG_GETARG_ARRAYTYPE_P_COPY(0);
	ArrayType  *fc_b = PG_GETARG_ARRAYTYPE_P_COPY(1);
	int			fc_na,
				fc_nb;
	int			fc_n;
	int		   *fc_da,
			   *fc_db;
	bool		fc_result;

	CHECKARRVALID(fc_a);
	CHECKARRVALID(fc_b);
	fc_na = ARRNELEMS(fc_a);
	fc_nb = ARRNELEMS(fc_b);
	fc_da = ARRPTR(fc_a);
	fc_db = ARRPTR(fc_b);

	fc_result = false;

	if (fc_na == fc_nb)
	{
		SORT(fc_a);
		SORT(fc_b);
		fc_result = true;

		for (fc_n = 0; fc_n < fc_na; fc_n++)
		{
			if (fc_da[fc_n] != fc_db[fc_n])
			{
				fc_result = false;
				break;
			}
		}
	}

	pfree(fc_a);
	pfree(fc_b);

	PG_RETURN_BOOL(fc_result);
}

/*	_int_overlap -- a 是否与 b 重叠?
 */
Datum _int_overlap(PG_FUNCTION_ARGS)
{
	ArrayType  *fc_a = PG_GETARG_ARRAYTYPE_P_COPY(0);
	ArrayType  *fc_b = PG_GETARG_ARRAYTYPE_P_COPY(1);
	bool		fc_result;

	CHECKARRVALID(fc_a);
	CHECKARRVALID(fc_b);
	if (ARRISEMPTY(fc_a) || ARRISEMPTY(fc_b))
		return false;

	SORT(fc_a);
	SORT(fc_b);

	fc_result = inner_int_overlap(fc_a, fc_b);

	pfree(fc_a);
	pfree(fc_b);

	PG_RETURN_BOOL(fc_result);
}

Datum _int_union(PG_FUNCTION_ARGS)
{
	ArrayType  *fc_a = PG_GETARG_ARRAYTYPE_P_COPY(0);
	ArrayType  *fc_b = PG_GETARG_ARRAYTYPE_P_COPY(1);
	ArrayType  *fc_result;

	CHECKARRVALID(fc_a);
	CHECKARRVALID(fc_b);

	SORT(fc_a);
	SORT(fc_b);

	fc_result = inner_int_union(fc_a, fc_b);

	pfree(fc_a);
	pfree(fc_b);

	PG_RETURN_POINTER(fc_result);
}

Datum _int_inter(PG_FUNCTION_ARGS)
{
	ArrayType  *fc_a = PG_GETARG_ARRAYTYPE_P_COPY(0);
	ArrayType  *fc_b = PG_GETARG_ARRAYTYPE_P_COPY(1);
	ArrayType  *fc_result;

	CHECKARRVALID(fc_a);
	CHECKARRVALID(fc_b);

	SORT(fc_a);
	SORT(fc_b);

	fc_result = inner_int_inter(fc_a, fc_b);

	pfree(fc_a);
	pfree(fc_b);

	PG_RETURN_POINTER(fc_result);
}


PG_FUNCTION_INFO_V1(intset);
PG_FUNCTION_INFO_V1(icount);
PG_FUNCTION_INFO_V1(sort);
PG_FUNCTION_INFO_V1(sort_asc);
PG_FUNCTION_INFO_V1(sort_desc);
PG_FUNCTION_INFO_V1(uniq);
PG_FUNCTION_INFO_V1(idx);
PG_FUNCTION_INFO_V1(subarray);
PG_FUNCTION_INFO_V1(intarray_push_elem);
PG_FUNCTION_INFO_V1(intarray_push_array);
PG_FUNCTION_INFO_V1(intarray_del_elem);
PG_FUNCTION_INFO_V1(intset_union_elem);
PG_FUNCTION_INFO_V1(intset_subtract);

Datum intset(PG_FUNCTION_ARGS)
{
	PG_RETURN_POINTER(int_to_intset(PG_GETARG_INT32(0)));
}

Datum icount(PG_FUNCTION_ARGS)
{
	ArrayType  *fc_a = PG_GETARG_ARRAYTYPE_P(0);
	int32		fc_count = ARRNELEMS(fc_a);

	PG_FREE_IF_COPY(fc_a, 0);
	PG_RETURN_INT32(fc_count);
}

Datum sort(PG_FUNCTION_ARGS)
{
	ArrayType  *fc_a = PG_GETARG_ARRAYTYPE_P_COPY(0);
	text	   *fc_dirstr = (fcinfo->nargs == 2) ? PG_GETARG_TEXT_PP(1) : NULL;
	int32		fc_dc = (fc_dirstr) ? VARSIZE_ANY_EXHDR(fc_dirstr) : 0;
	char	   *fc_d = (fc_dirstr) ? VARDATA_ANY(fc_dirstr) : NULL;
	int			fc_dir = -1;

	CHECKARRVALID(fc_a);
	if (ARRNELEMS(fc_a) < 2)
		PG_RETURN_POINTER(fc_a);

	if (fc_dirstr == NULL || (fc_dc == 3
						   && (fc_d[0] == 'A' || fc_d[0] == 'a')
						   && (fc_d[1] == 'S' || fc_d[1] == 's')
						   && (fc_d[2] == 'C' || fc_d[2] == 'c')))
		fc_dir = 1;
	else if (fc_dc == 4
			 && (fc_d[0] == 'D' || fc_d[0] == 'd')
			 && (fc_d[1] == 'E' || fc_d[1] == 'e')
			 && (fc_d[2] == 'S' || fc_d[2] == 's')
			 && (fc_d[3] == 'C' || fc_d[3] == 'c'))
		fc_dir = 0;
	if (fc_dir == -1)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("second parameter must be \"ASC\" or \"DESC\"")));
	QSORT(fc_a, fc_dir);
	PG_RETURN_POINTER(fc_a);
}

Datum sort_asc(PG_FUNCTION_ARGS)
{
	ArrayType  *fc_a = PG_GETARG_ARRAYTYPE_P_COPY(0);

	CHECKARRVALID(fc_a);
	QSORT(fc_a, 1);
	PG_RETURN_POINTER(fc_a);
}

Datum sort_desc(PG_FUNCTION_ARGS)
{
	ArrayType  *fc_a = PG_GETARG_ARRAYTYPE_P_COPY(0);

	CHECKARRVALID(fc_a);
	QSORT(fc_a, 0);
	PG_RETURN_POINTER(fc_a);
}

Datum uniq(PG_FUNCTION_ARGS)
{
	ArrayType  *fc_a = PG_GETARG_ARRAYTYPE_P_COPY(0);

	CHECKARRVALID(fc_a);
	if (ARRNELEMS(fc_a) < 2)
		PG_RETURN_POINTER(fc_a);
	fc_a = _int_unique(fc_a);
	PG_RETURN_POINTER(fc_a);
}

Datum idx(PG_FUNCTION_ARGS)
{
	ArrayType  *fc_a = PG_GETARG_ARRAYTYPE_P(0);
	int32		fc_result;

	CHECKARRVALID(fc_a);
	fc_result = ARRNELEMS(fc_a);
	if (fc_result)
		fc_result = intarray_match_first(fc_a, PG_GETARG_INT32(1));
	PG_FREE_IF_COPY(fc_a, 0);
	PG_RETURN_INT32(fc_result);
}

Datum subarray(PG_FUNCTION_ARGS)
{
	ArrayType  *fc_a = PG_GETARG_ARRAYTYPE_P(0);
	int32		fc_start = PG_GETARG_INT32(1);
	int32		fc_len = (fcinfo->nargs == 3) ? PG_GETARG_INT32(2) : 0;
	int32		fc_end = 0;
	int32		fc_c;
	ArrayType  *fc_result;

	fc_start = (fc_start > 0) ? fc_start - 1 : fc_start;

	CHECKARRVALID(fc_a);
	if (ARRISEMPTY(fc_a))
	{
		PG_FREE_IF_COPY(fc_a, 0);
		PG_RETURN_POINTER(new_intArrayType(0));
	}

	fc_c = ARRNELEMS(fc_a);

	if (fc_start < 0)
		fc_start = fc_c + fc_start;

	if (fc_len < 0)
		fc_end = fc_c + fc_len;
	else if (fc_len == 0)
		fc_end = fc_c;
	else
		fc_end = fc_start + fc_len;

	if (fc_end > fc_c)
		fc_end = fc_c;

	if (fc_start < 0)
		fc_start = 0;

	if (fc_start >= fc_end || fc_end <= 0)
	{
		PG_FREE_IF_COPY(fc_a, 0);
		PG_RETURN_POINTER(new_intArrayType(0));
	}

	fc_result = new_intArrayType(fc_end - fc_start);
	if (fc_end - fc_start > 0)
		memcpy(ARRPTR(fc_result), ARRPTR(fc_a) + fc_start, (fc_end - fc_start) * sizeof(int32));
	PG_FREE_IF_COPY(fc_a, 0);
	PG_RETURN_POINTER(fc_result);
}

Datum intarray_push_elem(PG_FUNCTION_ARGS)
{
	ArrayType  *fc_a = PG_GETARG_ARRAYTYPE_P(0);
	ArrayType  *fc_result;

	fc_result = intarray_add_elem(fc_a, PG_GETARG_INT32(1));
	PG_FREE_IF_COPY(fc_a, 0);
	PG_RETURN_POINTER(fc_result);
}

Datum intarray_push_array(PG_FUNCTION_ARGS)
{
	ArrayType  *fc_a = PG_GETARG_ARRAYTYPE_P(0);
	ArrayType  *fc_b = PG_GETARG_ARRAYTYPE_P(1);
	ArrayType  *fc_result;

	fc_result = intarray_concat_arrays(fc_a, fc_b);
	PG_FREE_IF_COPY(fc_a, 0);
	PG_FREE_IF_COPY(fc_b, 1);
	PG_RETURN_POINTER(fc_result);
}

Datum intarray_del_elem(PG_FUNCTION_ARGS)
{
	ArrayType  *fc_a = PG_GETARG_ARRAYTYPE_P_COPY(0);
	int32		fc_elem = PG_GETARG_INT32(1);
	int32		fc_c;
	int32	   *fc_aa;
	int32		fc_n = 0,
				fc_i;

	CHECKARRVALID(fc_a);
	if (!ARRISEMPTY(fc_a))
	{
		fc_c = ARRNELEMS(fc_a);
		fc_aa = ARRPTR(fc_a);
		for (fc_i = 0; fc_i < fc_c; fc_i++)
		{
			if (fc_aa[fc_i] != fc_elem)
			{
				if (fc_i > fc_n)
					fc_aa[fc_n++] = fc_aa[fc_i];
				else
					fc_n++;
			}
		}
		fc_a = resize_intArrayType(fc_a, fc_n);
	}
	PG_RETURN_POINTER(fc_a);
}

Datum intset_union_elem(PG_FUNCTION_ARGS)
{
	ArrayType  *fc_a = PG_GETARG_ARRAYTYPE_P(0);
	ArrayType  *fc_result;

	fc_result = intarray_add_elem(fc_a, PG_GETARG_INT32(1));
	PG_FREE_IF_COPY(fc_a, 0);
	QSORT(fc_result, 1);
	PG_RETURN_POINTER(_int_unique(fc_result));
}

Datum intset_subtract(PG_FUNCTION_ARGS)
{
	ArrayType  *fc_a = PG_GETARG_ARRAYTYPE_P_COPY(0);
	ArrayType  *fc_b = PG_GETARG_ARRAYTYPE_P_COPY(1);
	ArrayType  *fc_result;
	int32		fc_ca;
	int32		fc_cb;
	int32	   *fc_aa,
			   *fc_bb,
			   *fc_r;
	int32		fc_n = 0,
				fc_i = 0,
				fc_k = 0;

	CHECKARRVALID(fc_a);
	CHECKARRVALID(fc_b);

	QSORT(fc_a, 1);
	fc_a = _int_unique(fc_a);
	fc_ca = ARRNELEMS(fc_a);
	QSORT(fc_b, 1);
	fc_b = _int_unique(fc_b);
	fc_cb = ARRNELEMS(fc_b);
	fc_result = new_intArrayType(fc_ca);
	fc_aa = ARRPTR(fc_a);
	fc_bb = ARRPTR(fc_b);
	fc_r = ARRPTR(fc_result);
	while (fc_i < fc_ca)
	{
		if (fc_k == fc_cb || fc_aa[fc_i] < fc_bb[fc_k])
			fc_r[fc_n++] = fc_aa[fc_i++];
		else if (fc_aa[fc_i] == fc_bb[fc_k])
		{
			fc_i++;
			fc_k++;
		}
		else
			fc_k++;
	}
	fc_result = resize_intArrayType(fc_result, fc_n);
	pfree(fc_a);
	pfree(fc_b);
	PG_RETURN_POINTER(fc_result);
}
